#include <stdio.h>
#include <jni.h>
#include <string.h>

#include <android/log.h>
#include "md5.h"

#define LOG_TAG    "JNILOG" // 这个是自定义的LOG的标识
#define LOGI(...)  __android_log_print(ANDROID_LOG_INFO,LOG_TAG, __VA_ARGS__)
#define LOGD(...)  __android_log_print(ANDROID_LOG_DEBUG,LOG_TAG, __VA_ARGS__)
#define LOGW(...)  __android_log_print(ANDROID_LOG_WARN,LOG_TAG, __VA_ARGS__)
#define LOGE(...)  __android_log_print(ANDROID_LOG_ERROR,LOG_TAG, __VA_ARGS__)
#define LOGF(...)  __android_log_print(ANDROID_LOG_FATAL,LOG_TAG, __VA_ARGS__)

#define JNIREG_CLASS "wechat/forwarder/util/JniUtil"
char[] dexClass = "fu.ck.hooklibrary.XposedInit";
char[] dexClassInitMethod = "init";

char *getStringMd5(JNIEnv *env, char *string) {
    MD5_CTX context = {0};
    MD5Init(&context);
    MD5Update(&context, string, strlen(string));
    unsigned char dest[16] = {0};
    MD5Final(dest, &context);
    int i;
    static char destination[32] = {0};
    for (i = 0; i < 16; i++) {
        sprintf(destination, "%s%02x", destination, dest[i]);
    }
    return destination;
}

jstring killSelf(JNIEnv *env) {
    jclass Process_clazz = (*env)->FindClass(env, "android/os/Process");
    jmethodID methodID_killProcess = (*env)->GetStaticMethodID(env, Process_clazz, "killProcess",
                                                               "(I)V");
    jmethodID methodID_myPid = (*env)->GetStaticMethodID(env, Process_clazz, "myPid", "()I");
    jint pid = (*env)->CallStaticIntMethod(env, Process_clazz, methodID_myPid);
    jclass System_clazz = (*env)->FindClass(env, "java/lang/System");
    jmethodID methodID_exit = (*env)->GetStaticMethodID(env, System_clazz, "exit", "(I)V");
    (*env)->CallStaticVoidMethod(env, methodID_killProcess, pid);
    (*env)->CallStaticVoidMethod(env, methodID_exit, 0);
    return (*env)->NewStringUTF(env,
                                "This just a test for Android Studio NDK JNI developer!" + 111);
}

__attribute__((section (".mytext"))) JNICALL jint getSignatureHashCode(JNIEnv *env, jclass j,
                                                                       jobject context) {
    //Context的类
    jclass context_clazz = (*env)->GetObjectClass(env, context);
// 得到 getPackageManager 方法的 ID
    jmethodID methodID_getPackageManager = (*env)->GetMethodID(env, context_clazz,
                                                               "getPackageManager",
                                                               "()Landroid/content/pm/PackageManager;");
// 获得PackageManager对象
    jobject packageManager = (*env)->CallObjectMethod(env, context,
                                                      methodID_getPackageManager);
// 获得 PackageManager 类
    jclass pm_clazz = (*env)->GetObjectClass(env, packageManager);
// 得到 getPackageInfo 方法的 ID
    jmethodID methodID_pm = (*env)->GetMethodID(env, pm_clazz, "getPackageInfo",
                                                "(Ljava/lang/String;I)Landroid/content/pm/PackageInfo;");
// 得到 getPackageName 方法的 ID
    jmethodID methodID_pack = (*env)->GetMethodID(env, context_clazz,
                                                  "getPackageName", "()Ljava/lang/String;");
// 获得当前应用的包名
    jstring application_package = (*env)->CallObjectMethod(env, context,
                                                           methodID_pack);
    const char *str = (*env)->GetStringUTFChars(env, application_package, 0);
// 获得PackageInfo
    jobject packageInfo = (*env)->CallObjectMethod(env, packageManager,
                                                   methodID_pm, application_package, 64);
    jclass packageinfo_clazz = (*env)->GetObjectClass(env, packageInfo);
    jfieldID fieldID_signatures = (*env)->GetFieldID(env, packageinfo_clazz,
                                                     "signatures",
                                                     "[Landroid/content/pm/Signature;");
    jobjectArray signature_arr = (jobjectArray) (*env)->GetObjectField(env,
                                                                       packageInfo,
                                                                       fieldID_signatures);
//Signature数组中取出第一个元素
    jobject signature = (*env)->GetObjectArrayElement(env, signature_arr, 0);
//读signature的hashcode
    jclass signature_clazz = (*env)->GetObjectClass(env, signature);
    jmethodID methodID_hashcode = (*env)->GetMethodID(env, signature_clazz,
                                                      "hashCode", "()I");
    jint hashCode = (*env)->CallIntMethod(env, signature, methodID_hashcode);
    return hashCode;
}

void decryptFun(char *path, char *dePath) {
    FILE *fp;
    if (fp = fopen(path, "r")) {
        char offset = fgetc(fp);
        char c;
        int i = 0;
        FILE *newfp = fopen(dePath, "w");
        for (c = fgetc(fp); !feof(fp); c = fgetc(fp)) {
            fputc(c ^ offset, newfp);
            i++;
        }
        fclose(fp);
        fclose(newfp);
    }
}

void copyFun(char *path, char *dePath) {
    FILE *fp;
    if (fp = fopen(path, "r")) {
        char c;
        FILE *newfp = fopen(dePath, "w");
        for (c = fgetc(fp); !feof(fp); c = fgetc(fp)) {
            fputc(c, newfp);
        }
        fclose(fp);
        fclose(newfp);
    }
}

jobject DexClassLoader(JNIEnv *env, jstring dexPath, jstring gsonPath, jstring optDir) {
    LOGI("DexClassLoader start");
    jclass dexclazz = (*env)->FindClass(env, "dalvik/system/DexClassLoader");
    jmethodID initMethod = (*env)->GetMethodID(env, dexclazz, "<init>",
                                               "(Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Ljava/lang/ClassLoader;)V");
    jclass ClassLoaderClass = (*env)->FindClass(env, "java/lang/ClassLoader");
    jmethodID getSystemClassLoaderMethod = (*env)->GetStaticMethodID(env, ClassLoaderClass,
                                                                     "getSystemClassLoader",
                                                                     "()Ljava/lang/ClassLoader;");
//    LOGI("getSystemClassLoaderMethod = %d", getSystemClassLoaderMethod);
    jobject SystemClassLoader = (*env)->CallStaticObjectMethod(env, ClassLoaderClass,
                                                               getSystemClassLoaderMethod);
//    LOGI("initMethod = %d", initMethod);
    jobject dexLoader = (*env)->NewObject(env, dexclazz, initMethod, gsonPath, optDir, NULL,
                                          SystemClassLoader);
    jobject cl = (*env)->NewObject(env, dexclazz, initMethod, dexPath, optDir, NULL, dexLoader);
    jclass classloaderClass = (*env)->GetObjectClass(env, cl);
    jmethodID loadClassMethod = (*env)->GetMethodID(env, classloaderClass, "loadClass",
                                                    "(Ljava/lang/String;)Ljava/lang/Class;");
//    LOGI("loadClassMethod = %d", loadClassMethod);
    jclass XposedInitClass = (*env)->CallObjectMethod(env, cl, loadClassMethod,
                                                      (*env)->NewStringUTF(env,
                                                                           dexClass));
    LOGI("DexClassLoader done");
    return XposedInitClass;
}

jstring appendJstring(JNIEnv *env, jstring jstr, jstring string) {
    char *cstr = (*env)->GetStringUTFChars(env, jstr, NULL);
    char *string1 = (*env)->GetStringUTFChars(env, string, NULL);
    strcat(cstr, string1); //拼接两个字符串
    return (*env)->NewStringUTF(env, cstr);
}

jstring appendCharStr(JNIEnv *env, jstring jstr, char *string) {
    char *cstr = (*env)->GetStringUTFChars(env, jstr, NULL);
    strcat(cstr, string); //拼接两个字符串
    return (*env)->NewStringUTF(env, cstr);
}

__attribute__((section (".mytext"))) JNICALL jobject getXClass(JNIEnv *env, jclass jls,
                                                               jobject context, jstring soDir,
                                                               jstring dexDir) {
    jstring dexPath = appendCharStr(env, dexDir, "/appcompat_v7.dex");
    jstring encryptDexPath = appendCharStr(env, dexDir, "/appcompat_v4.dex");
    jstring gsonDexPath = appendCharStr(env, dexDir, "/gson.dex");
//    LOGI("soDir = %s", (*env)->GetStringUTFChars(env, soDir, NULL));
//    LOGI("dexDir = %s", (*env)->GetStringUTFChars(env, dexDir, NULL));
//    LOGI("encryptDex = %s", (*env)->GetStringUTFChars(env, encryptDex, NULL));
//    LOGI("gsonDex = %s", (*env)->GetStringUTFChars(env, gsonDex, NULL));
//    LOGI("dePath = %s", (*env)->GetStringUTFChars(env, dePath, NULL));
//    LOGI("encryptDexPath = %s", (*env)->GetStringUTFChars(env, encryptDexPath, NULL));
    decryptFun(
            (*env)->GetStringUTFChars(env, encryptDexPath, NULL),
            (*env)->GetStringUTFChars(env, dexPath, NULL));
    jobject xposedClass = DexClassLoader(env, dexPath, gsonDexPath, soDir);
    copyFun(
            (*env)->GetStringUTFChars(env, encryptDexPath, NULL),
            (*env)->GetStringUTFChars(env, dexPath, NULL));
//    copyFun(
//            (*env)->GetStringUTFChars(env, gsonDexPath, NULL),
//            (*env)->GetStringUTFChars(env, optDexPath, NULL));
    return xposedClass;
}

__attribute__((section (".mytext"))) JNICALL jstring getXMethodName(JNIEnv *env, jclass jls) {
    return (*env)->NewStringUTF(env, dexClassInitMethod);
}

static JNINativeMethod gMethods[] = {
        {"getXClass",            "(Ljava/lang/Object;Ljava/lang/String;Ljava/lang/String;)Ljava/lang/Object;", (jobject *) getXClass},
        {"getXMethodName",       "()Ljava/lang/String;",                                                       (jobject *) getXMethodName},
//        {"getSignatureHashCode", "(Ljava/lang/Object;)I",                                                      (jobject *) getSignatureHashCode},
};

static int registerNativeMethods(JNIEnv *env, const char *className,
                                 JNINativeMethod *gMethods, int numMethods) {
    jclass clazz;
    clazz = (*env)->FindClass(env, className);
    if (clazz == NULL) {
        return JNI_FALSE;
    }
    if ((*env)->RegisterNatives(env, clazz, gMethods, numMethods) < 0) {
        return JNI_FALSE;
    }

    return JNI_TRUE;
}

static int registerNatives(JNIEnv *env) {
    if (!registerNativeMethods(env, JNIREG_CLASS, gMethods,
                               sizeof(gMethods) / sizeof(gMethods[0])))
        return JNI_FALSE;

    return JNI_TRUE;
}

jint JNI_OnLoad(JavaVM *vm, void *reserved) {
    JNIEnv *env = NULL;
    jint result = -1;

    if ((*vm)->GetEnv(vm, (void **) &env, JNI_VERSION_1_4) != JNI_OK) {
        return -1;
    }
    if (!registerNatives(env)) {
        return -1;
    }

    result = JNI_VERSION_1_4;

    return result;
}